Skip to content

fix(opencode): close webfetch SSRF redirect bypass, fix IP range checks, harden response handling#36377

Open
sontani2017 wants to merge 1 commit into
anomalyco:devfrom
sontani2017:fix/webfetch-ssrf-hardening
Open

fix(opencode): close webfetch SSRF redirect bypass, fix IP range checks, harden response handling#36377
sontani2017 wants to merge 1 commit into
anomalyco:devfrom
sontani2017:fix/webfetch-ssrf-hardening

Conversation

@sontani2017

Copy link
Copy Markdown

Issue for this PR

Closes #36376

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Fixes the webfetch SSRF and robustness problems described in #36376:

  • The webfetch SSRF guard validated only the requested URL. The tool registry provides FetchHttpClient, and fetch follows redirects on its own — so a permitted public URL could 302 to http://169.254.169.254/ (or any internal host) and be fetched silently. This PR sets redirect: "manual" via the FetchHttpClient.RequestInit context and follows redirects in the tool itself, re-running the guard on every hop (max 5, non-http(s) targets refused). The Cloudflare 403-challenge retry is kept inside the loop.
  • Private-address checks were string-based and had holes: 0:0:0:0:0:0:0:1 passed as public, only the literal prefix fe80 of fe80::/10 was caught, and NAT64/6to4-embedded IPv4 plus several reserved IPv4 ranges (TEST-NETs, 198.18.0.0/15, 192.0.0.0/24, multicast/reserved) were missed. Replaced with a real IPv6 parser (:: expansion, dotted-quad tails, zone ids) and complete range tables. Hostname checks also cover *.internal and trailing dots (localhost.).
  • The 5MB cap was enforced only after buffering the whole body; the response is now streamed and aborted as soon as the cap is exceeded.
  • extractTextFromHTML skip-tracking no longer desyncs on void elements inside <script>/<style> regions (tag-name-based tracking).
  • Blocking private addresses outright would break fetching local dev servers (and the existing webfetch tests, which serve from Bun.serve on localhost), so there is an AllowPrivateFetch context reference: guard enforced by default, OPENCODE_WEBFETCH_ALLOW_PRIVATE=1 opts out (the refusal message says so), tests provide it explicitly.
  • .opencode/tool/github-pr-search.ts: page-based pagination replaces the offset math that could overlap/skip results, adds a state filter, stops sending Content-Type on GET; pure helpers of both .opencode tools split into .opencode/lib with unit tests. permission.ts/wildcard.ts changes are documentation comments only, pinned by a new permission/semantics.test.ts.

One known remaining gap, documented in the code: DNS rebinding cannot be fully closed without pinning the resolved IP into the connection, which fetch does not expose.

How did you verify your code works?

  • bun test in packages/opencode: webfetch integration suite (incl. new redirect-follow, redirect-cap, and guard-enforcement tests), webfetch-ssrf (49 IP/URL cases), webfetch-extract, and permission semantics — 175 tests pass
  • bun test in .opencode: 21 tests pass
  • bun turbo typecheck passes across the monorepo

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

- webfetch: follow redirects manually (redirect: manual via
  FetchHttpClient.RequestInit) and re-validate every hop against the
  SSRF guard, with a 5-redirect cap and non-http(s) targets refused;
  previously a permitted public URL could 302 to an internal address
  (e.g. 169.254.169.254) and be followed silently
- webfetch: replace string-matching IPv6 checks with a real parser
  (:: expansion, dotted-quad tails, zone ids) covering fe80::/10,
  fec0::/10, fc00::/7, ff00::/8, IPv4-mapped, NAT64 and 6to4 embeds;
  add missing IPv4 reserved ranges (192.0.0.0/24, TEST-NETs,
  198.18.0.0/15, multicast/reserved/broadcast)
- webfetch: block *.internal hosts and trailing-dot bypass
  (localhost.); stream response body with early abort at the 5MB cap
- webfetch: add AllowPrivateFetch context reference (default: guard
  enforced) with OPENCODE_WEBFETCH_ALLOW_PRIVATE env opt-out so local
  dev servers remain fetchable; integration tests provide it explicitly
- .opencode: repair github-pr-search.ts (file was truncated
  mid-statement and could not load); split pure helpers of both plugin
  tools into .opencode/lib with unit tests; page-based pagination and
  state filter for PR search
- tests: SSRF/extract/permission-semantics suites; redirect-follow,
  redirect-cap and guard-enforcement integration tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CreatorGhost added a commit to CreatorGhost/TheCode that referenced this pull request Jul 12, 2026
…ks (#9)

* fix(opencode): close webfetch SSRF redirect bypass and harden IP checks

Ported from upstream anomalyco#36377 (security substance only).

* fix(webfetch): address CodeRabbit review feedback

- Replace manual for loop with records.some() in assertPublicUrl
- Remove resolved IP from error message to prevent info leak
- Add regression test for redirect-to-private-target SSRF bypass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

webfetch tool: SSRF via redirects, incomplete private-IP checks, unbounded response buffering

1 participant